/*
 * Table.java
 *
 * Created on 8 November 2006, 22:34
 *
 */

package org.foment.joliage3.sql;

import java.awt.Container;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import javax.swing.JTextField;
import org.foment.joliage3.newt.DateField;
import org.foment.joliage3.newt.IntegerField;
import org.foment.joliage3.newt.SQLComboBox;

import static org.foment.joliage3.core.internationalization.Internationalization.i18n;

/**
 * Table class represents a table in the database.
 * @author ivan
 */
public class Table
{
  
  /**
   * Deletes a row in the ResultSet and notifies listeners about the event
   * If there is more than one operation you want to do on the ResultSet,
   * use getResultSet() and notifyTableChanged() afterwards
   * @throws java.sql.SQLException Exception throwed on an SQL error
   */
  public void deleteRow() throws SQLException {
    _resultset.deleteRow();
    notifyRowDeleted();
  };
  
  /**
   * Inserts a row in the ResultSet and notifies listeners about the event
   * If there is more than one operation you want to do on the ResultSet,
   * use getResultSet() and notifyTableChanged() afterwards
   * @throws java.sql.SQLException Exception throwed on an SQL error
   */
  public void insertRow() throws SQLException {
    _resultset.insertRow();
    notifyRowInserted();    
  }
  
  /**
   * Updates a row in the ResultSet and notifies listeners about the event
   * If there is more than one operation you want to do on the ResultSet,
   * use getResultSet() and notifyTableChanged() afterwards
   * @throws java.sql.SQLException Exception throwed on an SQL error
   */
  public void updateRow() throws SQLException {
    _resultset.updateRow();
    notifyRowUpdated();
  }
  
  /**
   * Notifies the listeners that table data is changed in a way that is difficult
   * to describe using other notification methods
   */
  public void notifyTableChanged() {
    for (TableListener listener: _listeners) {
      listener.tableDataChanged(this);
    }
  }
  
  /**
   * Notifies the listeners that the current row is deleted.
   */
  public void notifyRowDeleted() {
    int row = -1;
    try {
      row = _resultset.getRow();
    } catch (SQLException ex) {
      ex.printStackTrace();
    }
    for (TableListener listener: _listeners) {
      listener.tableRowDeleted(this, row);
    }
  }
  
  /**
   * Notifies the listeners that the current row is inserted.
   */
  public void notifyRowInserted() {
    for (TableListener listener: _listeners) {
      listener.tableRowInserted(this);
    }
  }
  
  /**
   * Notifies the listeners that the current row is updated.
   */
  public void notifyRowUpdated() {
    int row = -1;
    try {
      row = _resultset.getRow();
    } catch (SQLException ex) {
      ex.printStackTrace();
    }
    for (TableListener listener: _listeners) {
      listener.tableRowUpdated(this, row);
    }
  }
  
  public void reload() throws SQLException {
    pushState();
    readData();
    notifyTableChanged();
    popState();
  }
  
  /**
   * Creates a new instance of Table
   * @param tableName Name of the table in the sql database
   * @throws java.sql.SQLException Exception throwed on an SQL error
   */
  public Table(String tableName) throws SQLException {
    _tableName = tableName;
    readData();
  }
  
  private boolean readData() throws SQLException
  {
      if( conn == null )
      {
          conn = Database.getConnection();
      }
    java.sql.Statement statement =
        conn.createStatement(
      ResultSet.TYPE_SCROLL_SENSITIVE,
      ResultSet.CONCUR_UPDATABLE,
      ResultSet.HOLD_CURSORS_OVER_COMMIT);
    
    _resultset = statement.executeQuery("SELECT * FROM " + _tableName + ";");
    
    return true;
  }
  
  // Table data
  private String _tableName;
  private ResultSet _resultset;
  private java.awt.Container _editor;

  private static Connection conn = null; // One connection is shared among all tables
  
  /**
   * Gets the table name
   * @return the table name
   */
  public String getTableName() {
    return _tableName;
  }
  
  /**
   * Returns the ResultSet object of the table
   * @return ResultSet object
   */
  public ResultSet getResultSet() {
    return _resultset;
  }
  
  /**
   * Sets the component to be used as table editor component.
   * Fields in the container representing the fields from the table
   * must be in flg2_fieldName format
   * @param editor Container component
   */
  public void setDataEditor(java.awt.Container editor) {
    _editor = editor;
  }
  
  
  
  public void resetEditorData() throws SQLException {
    loadEditorDataFromCurrentRow();
  }
  
  /**
   * Loads the current row data into the editor
   * @throws java.sql.SQLException Exception throwed on an SQL error
   */
  public boolean editCurrentRow() throws SQLException {
    if (!checkEditorFieldsModified()) return false;
    loadEditorDataFromCurrentRow();
    return true;
  }
  
  private void loadEditorDataFromCurrentRow() throws SQLException {
    java.util.LinkedList<java.awt.Container> containerList = new java.util.LinkedList();
    containerList.add(_editor);
    
    while (!containerList.isEmpty()) {
      java.awt.Container current = containerList.getFirst();
      containerList.removeFirst();
      
      for (java.awt.Component component: current.getComponents()) {
        if (component.getName() != null && component.getName().startsWith("flg2_")) {
          if (IntegerField.class.isInstance(component)) {
            ((IntegerField)component).setValue(_resultset.getInt(component.getName().substring(5)));
          } else if (DateField.class.isInstance(component)) {
            java.util.Date date;
            try {
              date = _resultset.getDate(component.getName().substring(5));
            } catch (Exception ex) {
              date = null;
            }
            ((DateField)component).setValue(date);
          } else if (JTextField.class.isInstance(component)) {
            ((JTextField)component).setText(_resultset.getString(component.getName().substring(5)));
          } else if (SQLComboBox.class.isInstance(component)) {
            ((SQLComboBox)component).setValue(_resultset.getInt(component.getName().substring(5)));
          } else {
            System.err.println("Unknown component : " + component.getName());
          }
        } else {
          if (java.awt.Container.class.isInstance(component))
            containerList.addLast((java.awt.Container)component);
        }
      }
    }

    if (TableEditor.class.isInstance(_editor))
      ((TableEditor)_editor).activateEditor();
  }
  
  private void storeEditorDataToCurrentRow() throws SQLException {
    java.util.LinkedList<java.awt.Container> containerList = new java.util.LinkedList();
    containerList.add(_editor);
    
    while (!containerList.isEmpty()) {
      java.awt.Container current = containerList.getFirst();
      containerList.removeFirst();
      
      for (java.awt.Component component: current.getComponents()) {
        if (component.getName() != null && component.getName().startsWith("flg2_")) {
          if (IntegerField.class.isInstance(component)) {
            _resultset.updateInt(component.getName().substring(5), ((IntegerField)component).getValue());
          } else if (DateField.class.isInstance(component)) {
            java.util.Date date = ((DateField)component).getValue();
            _resultset.updateDate(component.getName().substring(5), 
              (date == null)?null:new java.sql.Date( date.getTime() )
            );
          } else if (JTextField.class.isInstance(component)) {
             //System.out.println("Treba izmeniti: " + _tableName + component.getName().substring(5) + " vrednost " + ((JTextField)component).getText());
             //System.out.println("Trenutna vrednost " + _resultset.getString(component.getName().substring(5)));
            _resultset.updateString(component.getName().substring(5), ((JTextField)component).getText());
            //System.out.println("Update-ovano: " + ((JTextField)component).getText());
          } else if (SQLComboBox.class.isInstance(component)) {
            _resultset.updateInt (component.getName().substring(5), ((SQLComboBox)component).getValue());
          } else {
            System.err.println("Unknown component : " + component.getName());
          }
        } else {
          if (java.awt.Container.class.isInstance(component))
            containerList.addLast((java.awt.Container)component);
        }
      }
    }
  }
  
  /**
   * Stores the editor data to database (current row)
   * @throws java.sql.SQLException Exception throwed on an SQL error
   */
  public void updateCurrentRow() throws SQLException {
    //System.out.println("Current row: " + _resultset.getString("EmployeeInsuranceFirstName"));
    storeEditorDataToCurrentRow();
    updateRow();
    if (TableEditor.class.isInstance(_editor))
      ((TableEditor)_editor).rowUpdated();
  }
  
  /**
   * Stores the editor data to database (as new row)
   * @throws java.sql.SQLException Exception throwed on an SQL error
   */
  public void insertCurrentRow() throws SQLException {
    _resultset.moveToInsertRow();
    storeEditorDataToCurrentRow();
    insertRow();
    if (TableEditor.class.isInstance(_editor))
      ((TableEditor)_editor).rowInserted();
  }
  
  /**
   * Moves the cursor in the ResultSet to specified row and activates editor
   * @param i Row index
   * @throws java.sql.SQLException Exception throwed on an SQL error
   * @return Returns true if editor is invoked.
   */
  public boolean editRow(int i) throws SQLException {
    if (!checkEditorFieldsModified()) return false;
    _resultset.absolute(i);
    editCurrentRow();
    return true;
  }
  
  private boolean checkEditorFieldsModified() {
    if (!TableEditor.class.isInstance(_editor)) return true;
    if (!((TableEditor)_editor).isDataModified()) return true;
    
    /*Object[] options = {
      i18n("Discard data"),
      i18n("Cancel action")
    };
    int n = javax.swing.JOptionPane.showOptionDialog(
      _editor, i18n("Current row is altered. What do you want to do with it?"),
      i18n("Data modified"),
      javax.swing.JOptionPane.YES_NO_OPTION,
      javax.swing.JOptionPane.QUESTION_MESSAGE,
      null,
      options,
      options[1]
    );
    
    if (n == 1) return false;

    return true;*/
    
    return (org.foment.joliage3.newt.Question.showQuestion(
      i18n("Data modified"),
      i18n("Current row is altered. What do you want to do with it?"),
      i18n("Discard data"),
      i18n("Cancel action")
    ) == org.foment.joliage3.newt.Question.STATUS_YES);
    
  }
  
  // Listeners:
  
  /**
   * Interface to be implemented by objects that need to be notified
   * about altering the table data.
   * @author ivan
   */
  public interface TableListener {

    /**
     * This method is used to notify the listener that a row is deleted
     * @param table Table that raised the event
     * @param row Index of the deleted row
     */
    public void tableRowDeleted(Table table, int row);

    /**
     * This method is used to notify the listener that a row is updated
     * @param table Table that raised the event
     * @param row Index of the updated row
     */
    public void tableRowUpdated(Table table, int row);

    /**
     * This method is used to notify the listener that a row is inserted
     * into the table
     * @param table Table that raised the event
     */
    public void tableRowInserted(Table table);

    /**
     * Table is altered in a way undescribable by row operations
     * @param table Table that raised the event
     */
    public void tableDataChanged(Table table);
  }

  
  private HashSet < TableListener > _listeners = new HashSet();
  
  /**
   * Registers a listener with a table
   * @return true if added. false if the specified listener is already listening
   * for the events of the specified table
   * @param tableListener Listener object
   */
  public boolean addListener(TableListener tableListener) {
    if (_listeners.contains(tableListener)) return false;
    _listeners.add(tableListener);
    return true;
  }
  
  /**
   * Removes a listener from the table
   * @return true if success. false if the specified listener was not listening
   * to the specified table
   * @param tableListener The listener to remove
   */
  public boolean removeListener(TableListener tableListener) {
    if (!_listeners.contains(tableListener)) return false;
    _listeners.remove(tableListener);
    return true;
  }
  
  /**
   * For notification on activation
   */
  public interface TableEditor {
    public void activateEditor();
    
    public void rowUpdated();
    public void rowInserted();
    
    public boolean isDataModified();
    public void setDataModified(boolean modified);
    
  }
  
  // Saved states
  private class SavedState {
    public int row;
    public boolean beforeFirst;
    public boolean afterLast;
  }
  
  java.util.LinkedList<SavedState> _states = new java.util.LinkedList();
  
  /**
   * Pushes the current table state to the stack. Must be used if you want to
   * move the ResultSet cursor.
   * @throws java.sql.SQLException On SQL error
   */
  public void pushState() throws SQLException {
    SavedState ss = new SavedState();
    ss.row = _resultset.getRow();
    ss.beforeFirst = _resultset.isBeforeFirst();
    ss.afterLast = _resultset.isAfterLast();
    _states.addFirst(ss);
  }
  
  /**
   * Removes the last saved table state from the stack without restoring
   * @throws java.sql.SQLException On SQL error
   */
  public void removeState() throws SQLException {
    _states.removeFirst();    
  } 
  
  /**
   * Restores the last saved table state from the stack.
   * @throws java.sql.SQLException On SQL error
   */
  public void popState() throws SQLException {
    SavedState ss = _states.getFirst();
    if (ss.beforeFirst) _resultset.beforeFirst();
    else if (ss.afterLast) _resultset.afterLast();
    else _resultset.absolute(ss.row);
    _states.removeFirst();    
  }
  
  private static HashMap < String, Table > _tables = new HashMap();
  
  /**
   * Returns the Table object for the specified table name
   * @param tableName table name
   * @return table object
   */
  public static Table getTable(String tableName) throws SQLException {
    if (!_tables.containsKey(tableName))
      _tables.put(tableName, new Table(tableName));
    return _tables.get(tableName);
  }
  
  public static void removeListenerFromAll(TableListener listener) {
    for (Table table: _tables.values())
      table.removeListener(listener);
  }
  
}
